/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <cstdlib>
#include "var_cache.h"
#include "napi_common.h"
#include "assert/__assert.h"
#include "thread/lock.h"

#include "hilog/log.h"
#define LOG_DOMAIN 0x0201
#define LOG_TAG "MY_TAG"

VarCache::VarCache() :
	jsenv_(nullptr) {
}

VarCache::~VarCache() {
}

VarCache* VarCache::Singleton() {
	static VarCache instance;    
    return &instance;
}

int VarCache::FindProperties(napi_value klass_or_instance, std::map<std::string, napi_value>& property_map)
{
	OH_LOG_INFO(LOG_APP, "VarCache::FindProperties start");
	napi_env env = VarCache::Singleton()->GetJSEnv();

	napi_value result;
	OH_LOG_INFO(LOG_APP, "VarCache::FindProperties calling napi_get_property_names klass_or_instance:%x", klass_or_instance);
	napi_status status = napi_get_property_names(env, klass_or_instance, &result);
	if (status != napi_ok) {
		return status;
	}

	uint32_t array_length;
	status  = napi_get_array_length(env,
									result,
									&array_length);
		if (status != napi_ok) {
			return status;
	}

	status = napi_ok;

	for (uint32_t idx = 0; idx < array_length; ++idx) {
		napi_value elem_name_value;
		status= napi_get_element(env,
								result,
								idx,
								&elem_name_value);
		if (status != napi_ok) {
			break;
		}
		size_t length;
		status = napi_get_value_string_utf8(env,
										elem_name_value,
										NULL,
										0,
										&length);
		if (status != napi_ok) {
			break;
		}                              
		char *property_name = new char[length + 1];
		status = napi_get_value_string_utf8(env,
										elem_name_value,
										property_name,
										length + 1,
										&length);
		if (status != napi_ok) {
			break;
		}
		napi_value property_value;
		status = napi_get_named_property(env,
										klass_or_instance,
										property_name,
										&property_value);
		if (status != napi_ok) {
			break;
		}
		OH_LOG_INFO(LOG_APP, "VarCache::FindProperties add property_name:%s", property_name);
		property_map.insert(std::make_pair(property_name, property_value));
		delete []property_name;
	}

	return status;
}
	
napi_value VarCache::GetOnePropertyOfClass(const std::map<std::string, napi_value>& property_map, const char* property_name)
{
	std::map<std::string, napi_value>::const_iterator it = property_map.find(property_name);
	if (it == property_map.end()) {
		return nullptr;
	} else {
		return it->second;
	}
}

napi_value VarCache::GetOnePropertyOfObj(napi_value obj, const char* property_name)
{
	napi_env env = VarCache::Singleton()->GetJSEnv();
	napi_value property_value;
	OH_LOG_INFO(LOG_APP, "VarCache::GetOnePropertyOfObj obj:%x, property_name:%s", obj, property_name);
	napi_status status = napi_get_named_property(env, obj, property_name, &property_value);
	if (status != napi_ok) {
		OH_LOG_INFO(LOG_APP, "VarCache::GetOnePropertyOfObj error:%d", status);
		return nullptr;
	}
    
	napi_ref property_ref = 0;
	napi_create_reference(env, property_value, 10, &property_ref);
	return property_value;
}

int VarCache::RegisterClass(const std::string& class_path, napi_value klass)
{
	OH_LOG_INFO(LOG_APP, "VarCache::RegisterClass class_path:%s", class_path.c_str());
#if 0
	mars::comm::ScopedSpinLock lock(class_map_lock_);
	std::pair<std::map<std::string, std::map<std::string, napi_value>>::iterator, bool> result = class_property_map_.insert(std::make_pair(class_path, std::map<std::string, napi_value>()));
	if (!result.second) {
		return -1;
	}
	FindProperties(klass, result.first->second);
#endif

	napi_ref klass_ref = 0;
	napi_env env = VarCache::Singleton()->GetJSEnv();
	OH_LOG_INFO(LOG_APP, "JS_CallStaticMethodByName RegisterClass env:%x, klass:%x, method:%x", env, klass, klass_ref);
	napi_create_reference(env, klass, 1, &klass_ref);
	class_map_.insert(std::make_pair(class_path, klass_ref));
	return 0;
}

std::map<std::string, napi_value>* VarCache::GetClassProperties(const char* class_path)
{
    mars::comm::ScopedSpinLock lock(class_map_lock_);
    std::map<std::string, std::map<std::string, napi_value>>::iterator result = class_property_map_.find(class_path);
  	if (result == class_property_map_.end()) {
  		return nullptr;
  	} else {
  		return &result->second;
  	}
}

napi_value VarCache::GetOnePropertyOfClass(const std::string& class_path, const std::string& property_name)
{
    mars::comm::ScopedSpinLock lock(class_map_lock_);
   	std::map<std::string, std::map<std::string, napi_value>>::iterator result = class_property_map_.find(class_path);
  	if (result == class_property_map_.end()) {
  		return nullptr;
  	}
  	std::map<std::string, napi_value>& property_map = result->second;

    std::map<std::string, napi_value>::iterator it = property_map.find(property_name);
  	if (it == property_map.end()) {
  		return nullptr;
  	} else {
  		return it->second;
  	}
}

napi_value VarCache::GetOneClass(const char* class_path)
{
	//mars::comm::ScopedSpinLock lock(class_map_lock_);
 	std::map<std::string, napi_ref>::iterator result = class_map_.find(class_path);
	if (result == class_map_.end()) {
		return nullptr;
	} else {
		napi_value klass;
		napi_env env = VarCache::Singleton()->GetJSEnv();
		napi_get_reference_value(env, result->second, &klass);
		return klass;
	}
}

void VarCache::GetStaticPropertyId(const char* const class_path,
                             		   const char* const property_name, 
                             		   std::string& property_id) 
{
   property_id = std::string(class_path) + std::string(".") + std::string(property_name);
}

void VarCache::GetPropertyId(napi_value obj,
							 const char* const class_path,
							 const char* const property_name,
							 std::string& property_id)
{
  uint64_t address = (uint64_t)obj;
   property_id = std::string(class_path) + std::string("@") + std::to_string(address) + std::string(".") + std::string(property_name);
}


/***************************************************************************************/

//mars initialize
/*
ETS method:
class mars
{
	bool LoadClass() {
	   
	    MarsNAPIPkg.AddClass("class1", class1,
	    											 "class2", class1);
	    //The following code will invoke AddClass napi interface.    											 
	}
}
*/
int GetNAPIStringValue(napi_env env, napi_value arg, std::string& str)
{
	str.clear();
	size_t length;
	napi_status status = napi_get_value_string_utf8(env,
										arg,
										NULL,
										0,
										&length);
	if (status != napi_ok) {
		return napi_invalid_arg;
	}

	str.resize(length + 1);
	status = napi_get_value_string_utf8(env,
										arg,
										&str[0],
										length + 1,
										&length);
	if (status != napi_ok) {
		return napi_string_expected;
	}
	str.resize(length);
	return napi_ok;
}

int GetNAPIStringValue(napi_env env, napi_value arg, std::string* strptr)
{
	std::string &str = *strptr;
	return GetNAPIStringValue(env, arg, str);
}


EXTERN_C_START

napi_value AddClass(napi_env env, napi_callback_info info) //packagename.AddClass(class1_path, class1, class2_path, class2, class3_path, class3, ...)
{
	OH_LOG_INFO(LOG_APP, "AddClass");
	size_t argc = 2;
	napi_value args[2];
	size_t ret;
	napi_value result;
	NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));

	VarCache *instance = VarCache::Singleton();
	instance->SetJSEnv(env);

	std::string class_name;
	if (napi_ok != (ret = GetNAPIStringValue(env, args[0], class_name))) {
		OH_LOG_INFO(LOG_APP, "AddClass get class name error:%d", result);
	}
	OH_LOG_INFO(LOG_APP, "calling RegisterClass class_name:%s", class_name.c_str());
	ret = instance->RegisterClass(class_name, args[1]);

	napi_create_int32(env, ret, &result);
#if 0
    size_t requireArgc = 1; 
    size_t argc = 1;
    napi_value anArg;
    napi_value *args = &anArg;
    napi_value result;
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
    if (argc < 2 || argc % 2 != 0) {
    	napi_create_int32(env, -1, &result);
    	return result;
    }
    
  	args = new napi_value[argc];
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
	 
    VarCache *instance = VarCache::Singleton();
    instance->SetJSEnv(env);
    int idx = 0, classNumber = 0;
    for (idx = 0; idx < (int)argc; ++idx) {
    	napi_valuetype valueTypeClassName; 
	    NAPI_CALL(env, napi_typeof(env, args[idx], &valueTypeClassName));
	    NAPI_ASSERT(env, valueTypeClassName == napi_string, "Wrong argument type. String expected.");
	    size_t length;
		std::string class_name;
		if (napi_ok != GetNAPIStringValue(env, args[idx], class_name)) {
			idx += 2;
			continue;
		}
		OH_LOG_INFO(LOG_APP, "AddClass class_name:%s", class_name.c_str());
	    ++idx;
	    napi_valuetype valueTypeClass; 
	    NAPI_CALL(env, napi_typeof(env, args[idx], &valueTypeClass)); 
	    NAPI_ASSERT(env, valueTypeClass == napi_function, "Wrong argument type. class expected."); 
	    ++idx;
	    int result = instance->RegisterClass(class_name, args[idx]);
	    if (result == 0) {
	    	++classNumber;
	    }
    }
         		
    napi_create_int32(env, classNumber, &result);
#endif
    return result;
} 

EXTERN_C_END
